package me.yangbajing.model

import org.bson.types.ObjectId
import org.squeryl.KeyedEntity
import org.joda.time.DateTime
import org.squeryl.dsl.ast.UpdateAssignment

import net.liftweb.common.{Loggable, Box}

import yangbajing.persistence.SquerylEntrypoint._
import yangbajing.util.Pagination
import yangbajing.util.Imports.Y

object MDocument extends Loggable {
  /**
   * 取消评论次数
   * @param docId
   * @param step
   * @return
   */
  def decrementCommentCount(docId: String, step: Long = 1L) =
    Y.tryBox(transaction(update(Entities.documents)(doc =>
      where(doc.id === docId) set (doc.comment_count.~ := doc.comment_count.~ - step))))

  /**
   * 增加评论次数
   * @param docId
   * @param step
   * @return
   */
  def incrementCommentCount(docId: String, step: Long = 1L) =
    Y.tryBox {
      transaction(update(Entities.documents)(doc =>
        where(doc.id === docId) set (doc.comment_count.~ := doc.comment_count.~ + step)
      ))
    }

  /**
   * 取消收藏次数
   * @param docId
   * @return
   */
  def decrementFavoriteCount(docId: String, step: Long = 1L) =
    Y.tryBox(transaction(update(Entities.documents)(doc =>
      where(doc.id === docId) set (doc.favorite_count.~ := doc.favorite_count.~ - step))))

  /**
   * 增加收藏次数
   * @param docId
   * @param step
   * @return
   */
  def incrementFavoriteCount(docId: String, step: Long = 1L) =
    Y.tryBox(transaction(update(Entities.documents)(doc =>
      where(doc.id === docId) set (doc.favorite_count.~ := doc.favorite_count.~ + step))))

  /**
   *
   * @param id 只能使用doc.id
   * @return
   */
  def removeById(id: String): Int =
    transaction {
      Entities.documents.deleteWhere(_.id === id)
      Entities.tags.deleteWhere(_.ref_id === id)
    }

  /**
   *
   * @param id 可使用doc.id或doc.slug
   * @return
   */
  def findOne(id: String): Box[MDocument] =
    transaction(from(Entities.documents)(doc =>
      where(
        if (ObjectId.isValid(id)) doc.id === id else doc.slug === id
      ) select doc).singleOption)

  def size(creator: Option[String] = None,
           gteCreatedAt: Option[DateTime] = None,
           lteCreatedAt: Option[DateTime] = None,
           categoryId: Option[String] = None,
           tagId: Option[String] = None): Long =
    transaction(tagId match {
      case Some(tid) =>
        from(Entities.documents, Entities.tags)((d, t) =>
          _whereAndTag(d, t, tid, creator, gteCreatedAt, lteCreatedAt, categoryId) compute count(d.id))
      case None =>
        from(Entities.documents)(doc =>
          _whereOnlyDoc(doc, creator, gteCreatedAt, lteCreatedAt, categoryId) compute count(doc.id))
    })

  def insert(document: MDocument, insertTags: List[String]): Box[MDocument] =
    Y.tryBox(transaction {
      val doc = insertTags match {
        case Nil => document
        case ls => document.copy(tags = Some(ls.mkString(",")))
      }
      val result = Entities.documents insert doc
      MTag.insertFormRef(document.id, insertTags)

      result
    })

  def modify(id: String,
             category: Option[String] = None,
             title: Option[String] = None,
             description: Option[String] = None,
             slug: Option[String] = None,
             content: Option[String] = None,
             subtitle: Option[String] = None,
             commentCount: Option[Long] = None,
             favoriteCount: Option[Long] = None,
             tags: Option[String] = None,
             removeTags: List[String] = Nil,
             insertTags: List[String] = Nil): Box[Int] =
    Y.tryBox {
      def _set(doc: MDocument): List[UpdateAssignment] =
        List(
          category map (doc.category := _),
          title map (doc.title := _),
          Some(doc.description := description),
          slug map (_ => doc.slug := slug),
          subtitle map (_ => doc.subtitle := subtitle),
          content map (doc.content := _),
          tags map (_ => doc.tags := tags),
          commentCount map (doc.comment_count := _),
          favoriteCount map (doc.favorite_count := _)).flatten

      transaction {
        val result =
          update(Entities.documents)(doc =>
            where(
              doc.id === id
            ) set (_set(doc): _*))

        MTag.removeFromRef(id, removeTags)
        MTag.insertFormRef(id, insertTags)

        result
      }
    }

  /**
   *
   * @param limit
   * @param creator
   * @param gteCreatedAt
   * @param lteCreatedAt
   * @param categoryId
   * @param tagId
   * @return List[(Id, Title, Slug)]
   */
  def idTitles(limit: Int,
               creator: Option[String] = None,
               gteCreatedAt: Option[DateTime] = None,
               lteCreatedAt: Option[DateTime] = None,
               categoryId: Option[String] = None,
               tagId: Option[String] = None): List[(String, String, Option[String])] =
    transaction {
      val query =
        tagId match {
          case Some(tid) =>
            from(Entities.documents, Entities.tags)((d, t) =>
              _whereAndTag(d, t, tid, creator, gteCreatedAt, lteCreatedAt, categoryId)
                select Tuple3(d.id, d.title, d.slug) orderBy d.created_at.desc)
          case None =>
            from(Entities.documents)(d =>
              _whereOnlyDoc(d, creator, gteCreatedAt, lteCreatedAt, categoryId)
                select Tuple3(d.id, d.title, d.slug) orderBy d.created_at.desc)
        }

      query.page(0, limit).toList
    }

  def findAll(offset: Int, limit: Int,
              creator: Option[String] = None,
              gteCreatedAt: Option[DateTime] = None,
              lteCreatedAt: Option[DateTime] = None,
              categoryId: Option[String] = None,
              tagId: Option[String] = None): List[MDocument] =
    transaction {
      val query =
        tagId match {
          case Some(tid) =>
            from(Entities.documents, Entities.tags)((d, t) =>
              _whereAndTag(d, t, tid, creator, gteCreatedAt, lteCreatedAt, categoryId)
                select d orderBy d.created_at.desc)
          case None =>
            from(Entities.documents)(d =>
              _whereOnlyDoc(d, creator, gteCreatedAt, lteCreatedAt, categoryId)
                select d orderBy d.created_at.desc)
        }

      query.page(offset, limit).toList
    }

  def pagination(_curPage: Int, _limit: Int,
                 creator: Option[String] = None,
                 gteCreatedAt: Option[DateTime] = None,
                 lteCreatedAt: Option[DateTime] = None,
                 categoryId: Option[String] = None,
                 tagId: Option[String] = None) =
    new Pagination[MDocument] {
      val curPage = _curPage
      val limit = _limit
      val total = size(creator, gteCreatedAt, lteCreatedAt, categoryId, tagId)
      val page = findAll(offset, limit, creator, gteCreatedAt, lteCreatedAt, categoryId, tagId)
    }

  private def _whereOnlyDoc(doc: MDocument,
                            creator: Option[String],
                            gteCreatedAt: Option[DateTime],
                            lteCreatedAt: Option[DateTime],
                            categoryId: Option[String]) =
    where(
      doc.creator === creator.? and
        (doc.created_at gte gteCreatedAt.?) and
        (doc.created_at lte lteCreatedAt.?) and
        (doc.category === categoryId.?)
    )

  private def _whereAndTag(doc: MDocument, tag: MTag, tagId: String,
                           creator: Option[String],
                           gteCreatedAt: Option[DateTime],
                           lteCreatedAt: Option[DateTime],
                           categoryId: Option[String]) =
    where(
      (tag.tag_id === tagId) and
        (doc.id === tag.ref_id) and
        (doc.creator === creator.?) and
        (doc.created_at gte gteCreatedAt.?) and
        (doc.created_at lte lteCreatedAt.?) and
        (doc.category === categoryId.?)
    )
}

case class MDocument(
                      creator: String,
                      category: String,
                      title: String,
                      content: String,
                      subtitle: Option[String] = None,
                      description: Option[String] = None,
                      slug: Option[String] = None, // 短名
                      comment_count: Long = 0L,
                      favorite_count: Long = 0L,
                      created_at: DateTime = DateTime.now(),
                      tags: Option[String] = None, // 缓存tags
                      id: String = ObjectId.get().toString) extends KeyedEntity[String] {

  def slugOrId = slug getOrElse id

  lazy val tagIds: List[String] = tags map (_.split(',').toList) getOrElse Nil
}
